#include "WorldDumper.h"

#include <boost/property_tree/xml_parser.hpp>

#include "World.h"
#include "WorldInfoController.h"
#include "Ground.h"
#include "GroundTypes.h"
#include "Human.h"
#include "Tree.h"
#include "FerrisWheel.h"
#include "SharedConstants.h"
#include "Balloon.h"
#include "Bench.h"
#include "Water.h"
#include "FindWaterAreas.h"

#include <fstream>

using boost::property_tree::ptree;

namespace
  {
  ptree DumpSize(size_t i_size_x, size_t i_size_y)
    {
    ptree tree;

    tree.put("x", i_size_x);
    tree.put("y", i_size_y);

    return tree;
    }

  ptree DumpGroundTypes(const Ground &i_ground, size_t i_size_x, size_t i_size_y)
    {
    ptree tree;
    for(size_t i = 0; i < i_size_x; ++i)
      {
      for(size_t j = 0; j < i_size_y; ++j)
        {
        ptree child;

        child.put("x", i);
        child.put("y", j);
        child.put("value", i_ground.GetGroundType(i, j));

        tree.add_child("cell", child);
        }
      }
    return tree;
    }

  ptree DumpHuman(Human* human)
    {
    ptree tree;
    TVector3 position = human->GetPosition();

    tree.put("x", position.X());
    tree.put("y", position.Y());
    tree.put("z", position.Z());

    if (human->GetBalloon() != nullptr)
      {
      tree.put("balloon", 1);
      tree.put("color", human->GetBalloon()->GetColor());
      }
    else
      {
      tree.put("balloon", 0);
      }

    return tree;
    }

  ptree DumpHeightMap(const Ground &i_ground, size_t i_size_x, size_t i_size_y)
    {
    ptree tree;
    const HeightMap & height_map = i_ground.GetHeightMap();
    for(size_t i = 0; i < i_size_x; ++i)
      {
      for(size_t j = 0; j < i_size_y; ++j)
        {
        ptree child;

        child.put("x", i);
        child.put("y", j);
        child.put("value", height_map.Get(i, j));

        tree.add_child("node_height", child);
        }
      }
    return tree;
    }

  ptree DumpTree(Tree* i_tree)
    {
    ptree tree;
    TVector3 position = i_tree->GetPosition();

    tree.put("x", position.X());
    tree.put("y", position.Y());
    tree.put("z", position.Z());

    return tree;
    }

  ptree DumpFerrisWheel(FerrisWheel * i_wheel)
    {
    ptree tree;
    TVector3 position = i_wheel->GetPosition();

    tree.put("x", position.X());
    tree.put("y", position.Y());
    tree.put("z", position.Z());

    return tree;
    }

  ptree DumpBench(Bench* i_bench)
    {
    ptree tree;
    TVector3 position = i_bench->GetPosition();

    tree.put("x", position.X());
    tree.put("y", position.Y());

    return tree;
    }

  ptree DumpMoney(int i_money_amount)
    {
    ptree tree;

    tree.put("money", i_money_amount);

    return tree;
    }
	bool FileExists(const std::string&  i_filename)
	{
		return std::ifstream(i_filename) != NULL;
	}

  ptree DumpBalloonsShop(BalloonsShop* i_shop)
    {
    ptree tree;
    TVector3 position = i_shop->GetPosition();

    tree.put("x", position.X());
    tree.put("y", position.Y());
    tree.put("z", position.Z());

    return tree;
  }

  ptree DumpWaterPoint(THMapPoint i_point)
    {
    ptree tree;
    tree.put("i", i_point.first);
    tree.put("j", i_point.second);
    return tree;
    }

  ptree DumpWater(std::shared_ptr<Water> ip_water)
    {
    ptree tree;

    double water_level  = ip_water->GetArea().level;
    auto water_points = ip_water->GetArea().points;

    tree.put("level", water_level);
    
    ptree points_tree;
    for (auto it : water_points)
      {
      points_tree.add_child("Point", DumpWaterPoint(it));
      }
    
    tree.add_child("Points", points_tree);

    return tree;
    }

  std::shared_ptr<Water> RestoreSingleWater(ptree i_ptree, size_t i_size_x, size_t i_size_y)
    {
    double water_level = i_ptree.get<double>("level");
    THMapArea water_area;
    water_area.level = water_level;

    auto points_tree = i_ptree.get_child("Points");

    for (auto points_it = points_tree.begin(); points_it != points_tree.end(); ++points_it )
      {
      size_t index_i = points_it->second.get<size_t>("i");
      size_t index_j = points_it->second.get<size_t>("j");
      water_area.points.insert(std::make_pair(index_i, index_j));
      }

    std::shared_ptr<Model3D> p_water_model = HeightMapUtils::GenerateWaterModel(water_area, i_size_x, i_size_y);
    std::shared_ptr<Water> p_water(new Water(GroundConstants::ORIGIN_VECTOR, water_area, p_water_model));

    return p_water;
    }

  }

WorldDumper::WorldDumper(World &io_world, GameController& i_game_controller, const std::string &i_filename)
  :m_world(io_world), m_game_controller(i_game_controller), m_filename(i_filename)
  {
  }

void WorldDumper::Dump()
  {
  m_ptree.clear();

  _DumpGround();
  _DumpHumans();
  _DumpTrees();
  _DumpFerrisWheels();
  _DumpBalloonsShops();
  _DumpBenches();
  _DumpGameInfo();
  _DumpWater();

  ptree to_dump;
  to_dump.add_child("HeavenWorld", m_ptree);

  boost::property_tree::xml_writer_settings<char> settings('\t', 1);
  boost::property_tree::write_xml(m_filename, to_dump, std::locale(), settings);
  }

bool WorldDumper::Restore()
  {
	if (!FileExists(m_filename)) //file with save may not exist and dumper can't read anything, so we do nothing
		{
		return false;
		}
  m_ptree.clear();
  m_game_controller.Reset();

  ptree to_restore;
  boost::property_tree::read_xml(m_filename, to_restore, boost::property_tree::xml_parser::trim_whitespace);
  m_ptree = to_restore.get_child("HeavenWorld");

  auto height_map = _RestoreHeightMap();
  auto ground_types = _RestoreGroundTypes();
  m_world.ClearModels();

  std::unique_ptr<Ground> new_ground(new Ground(height_map, GroundConstants::CELL_SIZE, GroundConstants::WATER_LEVEL));
  new_ground->ResetGroundTypes(ground_types);
  m_world.ResetGround(new_ground);

  _RestoreHumans();
  _RestoreTrees();
  _RestoreFerrisWheels();
  _RestoreBalloonsShops();
  _RestoreBenches();
  _RestoreWater();

  m_world.GetWorldInfoController()->UpdateGroundInfo();
  m_world.GetWorldInfoController()->UpdateWaterInfo();
  m_world.GetWorldInfoController()->UpdateInfo(m_world.Renderable());

  m_game_controller.LandscapeChanged(*m_world.GetGround());
  _RestoreGameInfo();
	return true;
  }

void WorldDumper::_DumpGround()
  {
  ptree tree;
  auto ground = m_world.GetGround();
  const size_t size_x = ground->GetHeightMap().GetSizeX() - 1; // GroundTypes size is one lesser then HeightMap size
  const size_t size_y = ground->GetHeightMap().GetSizeY() - 1;

  auto size_tree = DumpSize(size_x, size_y);
  auto ground_types_tree = DumpGroundTypes(*ground, size_x, size_y);

  tree.add_child("GroundTypesSize", size_tree);
  tree.add_child("GroundTypes", ground_types_tree);

  const size_t heightMap_size_x = ground->GetHeightMap().GetSizeX();
  const size_t heightMap_size_y = ground->GetHeightMap().GetSizeY();

  auto heightMap_size_tree = DumpSize(heightMap_size_x, heightMap_size_y);
  auto height_map_tree = DumpHeightMap(*ground, heightMap_size_x, heightMap_size_y);

  tree.add_child("HeightMapSize", heightMap_size_tree);
  tree.add_child("HeightMap", height_map_tree);

  m_ptree.add_child("Ground", tree);
  }

TGroundTypeMap WorldDumper::_RestoreGroundTypes()
  {
  size_t size_x = m_ptree.get("Ground.GroundTypesSize.x", 0);
  size_t size_y = m_ptree.get("Ground.GroundTypesSize.y", 0);

  TGroundTypeMap ground_type;
  auto tree = m_ptree.get_child("Ground.GroundTypes");
  ground_type.resize(size_x);
  for(size_t i = 0; i < size_x; i++)
    {
    ground_type[i].resize(size_y);
    }

  for(auto it = tree.begin(); it != tree.end(); it++)
    {
    size_t x = it->second.get("x", 0);
    size_t y = it->second.get("y", 0);
    EGroundType t = (EGroundType) it->second.get("value", 0);

    ground_type[x][y] = t;
    if(t == GT_WALKWAY)
      m_game_controller.WalkwayCreated(false);
    }

  return ground_type;
  }


void WorldDumper::_DumpHumans()
  {
  ptree tree;
  for(Human* human : m_world.GetItems<Human>())
    {
    auto human_tree = DumpHuman(human);
    tree.add_child("Human", human_tree);
    }

  m_ptree.add_child("Humans", tree);
  }

void WorldDumper::_RestoreHumans()
  {
  auto humans_tree = m_ptree.get_child("Humans");

  for(auto it = humans_tree.begin(); it != humans_tree.end(); it++)
    {
    double X = it->second.get<double>("x");
    double Y = it->second.get<double>("y");
    double Z = it->second.get<double>("z");

    bool has_balloon = it->second.get<bool>("balloon");
    if (has_balloon)
      {
      EBalloonColor color = (EBalloonColor) it->second.get("color", 0);
      m_world.AddHuman(TVector3(X, Y, Z), color);
      }
    else
      {
      m_world.AddHuman(TVector3(X, Y, Z));
      }
    }
  }

HeightMap WorldDumper::_RestoreHeightMap()
  {
  size_t size_x = m_ptree.get("Ground.HeightMapSize.x", 0);
  size_t size_y = m_ptree.get("Ground.HeightMapSize.y", 0);
  auto tree = m_ptree.get_child("Ground.HeightMap");
  HeightMap height_map(size_x, size_y);
  for(auto it = tree.begin(); it != tree.end(); it++)
    {
    size_t x = it->second.get<size_t>("x");
    size_t y = it->second.get<size_t>("y");
    double t = it->second.get<double>("value");

    height_map.Set(x, y, t);
    }
  return height_map;
  }

void WorldDumper::_DumpTrees()
  {
  ptree result_tree;
  for(Tree* tree : m_world.GetItems<Tree>())
    {
    auto tree_tree = DumpTree(tree);
    result_tree.add_child("Tree", tree_tree);
    }

  m_ptree.add_child("Trees", result_tree);
  }

void WorldDumper::_RestoreTrees()
  {
  auto trees_tree = m_ptree.get_child("Trees");

  for(auto it = trees_tree.begin(); it != trees_tree.end(); it++)
    {
    double X = it->second.get<double>("x");
    double Y = it->second.get<double>("y");
    double Z = it->second.get<double>("z");

    m_world.AddTree(TVector3(X, Y, Z));
    m_game_controller.TreePlaced(false);
    }
  }

void WorldDumper::_DumpFerrisWheels()
  {
  ptree result_tree;
  for(FerrisWheel* wheel : m_world.GetItems<FerrisWheel>())
    {
    auto wheels_tree = DumpFerrisWheel(wheel);
    result_tree.add_child("FerrisWheel", wheels_tree);
    }

  m_ptree.add_child("FerrisWheels", result_tree);
  }

void WorldDumper::_RestoreFerrisWheels()
  {
  auto wheels_tree = m_ptree.get_child("FerrisWheels");

  for(auto it = wheels_tree.begin(); it != wheels_tree.end(); it++)
    {
    double X = it->second.get<double>("x");
    double Y = it->second.get<double>("y");
    double Z = it->second.get<double>("z");

    m_world.AddFerrisWheel(TVector3(X, Y, Z));
    m_game_controller.FerrisWheelPlaced(false);
    }
  }

void WorldDumper::_DumpBenches()
  {
  ptree result_tree;
  for (Bench* bench : m_world.GetItems<Bench>())
    {
    auto bench_tree = DumpBench(bench);
    result_tree.add_child("Bench", bench_tree);
    }
  m_ptree.add_child("Benches", result_tree);
  }

void WorldDumper::_RestoreBenches()
  {
  auto benches_tree = m_ptree.get_child("Benches");
  for (auto it : benches_tree)
    {
    double X = it.second.get<double>("x");
    double Y = it.second.get<double>("y");
    m_world.AddBench(TVector2(X, Y));
    m_game_controller.BenchPlaced(false);
    }
  }

void WorldDumper::_DumpGameInfo()
  {
  ptree game_tree;

  game_tree = DumpMoney(m_game_controller.GetMoney());

  m_ptree.add_child("GameInfo", game_tree);
  }

void WorldDumper::_RestoreGameInfo()
  {
  auto game_info_tree = m_ptree.get_child("GameInfo");

  int money = game_info_tree.get<int>("money");

  m_game_controller.SetMoney(money);
  }

void WorldDumper::_DumpBalloonsShops()
  {
  ptree result_tree;
  for(BalloonsShop* shop : m_world.GetItems<BalloonsShop>())
    {
    auto shops_tree = DumpBalloonsShop(shop);
    result_tree.add_child("BalloonsShop", shops_tree);
    }

  m_ptree.add_child("BalloonsShops", result_tree);
  }

void WorldDumper::_RestoreBalloonsShops()
  {
  auto shops_tree = m_ptree.get_child("BalloonsShops");

  for(auto it = shops_tree.begin(); it != shops_tree.end(); it++)
    {
    double X = it->second.get<double>("x");
    double Y = it->second.get<double>("y");
    double Z = it->second.get<double>("z");

    m_world.AddBalloonsShop(TVector3(X, Y, Z));
    m_game_controller.BalloonsShopPlaced(false);
    }
  }

void WorldDumper::_DumpWater()
  {
  ptree result_tree;
  std::vector<std::shared_ptr<Water>> water_objects = m_world.GetGround()->GetWaterModels();
  
  for (auto it : water_objects)
    {
    result_tree.add_child("Water", DumpWater(it));
    }
  m_ptree.add_child("WaterModels", result_tree);
  }

void WorldDumper::_RestoreWater()
  {
  auto water_models_tree = m_ptree.get_child("WaterModels");

  size_t size_x = m_world.GetGround()->GetHeightMap().GetSizeX();
  size_t size_y = m_world.GetGround()->GetHeightMap().GetSizeY();

  for (auto it_water = water_models_tree.begin(); it_water != water_models_tree.end(); ++it_water)
    {
    m_world.GetGround()->AddWaterModel(RestoreSingleWater(it_water->second, size_x, size_y));
    m_game_controller.WaterCreated(false);
    }
  }
